use super::*;

#[test]
fn test_simple_add() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let result = eval("(+ 1 2)", &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer(3)));
}

#[test]
fn test_simple_sub() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let result = eval("(- 1.0 2)", &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Float(-1.0)));
}

// #[test]
// fn test_str_add() {
//     let mut env = Rc::new(RefCell::new(Env::new()));
//     let result = eval("(+ \"Raleigh\" \"Durham\")", &mut env).unwrap();
//     assert_eq!(result, Rc::new(Object::String("RaleighDurham".to_string())));
// }

// #[test]
// fn test_str_eq_false() {
//     let mut env = Rc::new(RefCell::new(Env::new()));
//     let result = eval("(= \"Raleigh\" \"Durham\")", &mut env).unwrap();
//     assert_eq!(result, Rc::new(Object::Bool(false)));
// }

// #[test]
// fn test_str_eq_true() {
//     let mut env = Rc::new(RefCell::new(Env::new()));
//     let result = eval("(= \"Raleigh\" \"Raleigh\")", &mut env).unwrap();
//     assert_eq!(result, Rc::new(Object::Bool(true)));
// }

// #[test]
// fn test_greater_than_str() {
//     let mut env = Rc::new(RefCell::new(Env::new()));
//     let result = eval("(> \"Raleigh\" \"Durham\")", &mut env).unwrap();
//     assert_eq!(result, Rc::new(Object::Bool(true)));
// }

// #[test]
// fn test_less_than_str() {
//     let mut env = Rc::new(RefCell::new(Env::new()));
//     let result = eval("(< \"abcd\" \"abef\")", &mut env).unwrap();
//     assert_eq!(result, Rc::new(Object::Bool(true)));
// }

// #[test]
// fn test_str_with_spaces() {
//     let mut env = Rc::new(RefCell::new(Env::new()));
//     let result = eval("(+ \"Raleigh \" \"Durham\")", &mut env).unwrap();
//     assert_eq!(result, Rc::new(Object::String("Raleigh Durham".to_string())));
// }

// #[test]
// fn test_str_with_spaces_2() {
//     let mut env = Rc::new(RefCell::new(Env::new()));
//     let program = "
//     (
//         (define fruits \"apples mangoes bananas \")
//         (define vegetables \"carrots broccoli\")
//         (+ fruits vegetables)
//     )
//     ";
//     let result = eval(program, &mut env).unwrap();
//     assert_eq!(
//         result,
//         Rc::new(Object::List(Rc::new(vec![Rc::new(Object::String(
//             "apples mangoes bananas carrots broccoli".to_string()
//         )]))
//     );
// }

#[test]
fn test_greater_than_int() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let result = eval("(> 10 20)", &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Bool(false)));
}

#[test]
fn test_less_than_int() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let result = eval("(< 21.0 20.0)", &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Bool(false)));
}

#[test]
fn test_modulo() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let result = eval("(% 21.0 20.0)", &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Float(1.0)));
}

#[test]
fn test_area_of_a_circle_float() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define r 5.0)
            (define pi 3.14)
            (* pi (* r r))
        )";
    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Float((3.14 * 5.0 * 5.0) as f64)));
}

#[test]
fn test_range_no_stride() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "(range 0 11)";
    let result = eval(program, &mut env).unwrap();
    assert_eq!(
        result,
        Rc::new(Object::List(vec![
            Rc::new(Object::Integer(0)),
            Rc::new(Object::Integer(1)),
            Rc::new(Object::Integer(2)),
            Rc::new(Object::Integer(3)),
            Rc::new(Object::Integer(4)),
            Rc::new(Object::Integer(5)),
            Rc::new(Object::Integer(6)),
            Rc::new(Object::Integer(7)),
            Rc::new(Object::Integer(8)),
            Rc::new(Object::Integer(9)),
            Rc::new(Object::Integer(10))
        ]))
    );
}

#[test]
fn test_range_with_stride() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "(range 0 10 3)";
    let result = eval(program, &mut env).unwrap();
    assert_eq!(
        result,
        Rc::new(Object::List(vec![
            Rc::new(Object::Integer(0)),
            Rc::new(Object::Integer(3)),
            Rc::new(Object::Integer(6)),
            Rc::new(Object::Integer(9)),
        ]))
    );
}

#[test]
fn test_area_of_a_circle() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define r 10)
            (define pi 314)
            (* pi (* r r))
        )";
    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((314 * 10 * 10) as i64)));
}

#[test]
fn test_sqr_function() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define sqr (lambda (r) (* r r))) 
            (sqr 10)
        )";
    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((10 * 10) as i64)));
}

#[test]
fn test_map() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define sqr (lambda (r) (* r r)))
            (define l (list 1 2 3 4 5))
            (map sqr l)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(
        result,
        Rc::new(Object::List(vec![
            Rc::new(Object::Integer(1)),
            Rc::new(Object::Integer(4)),
            Rc::new(Object::Integer(9)),
            Rc::new(Object::Integer(16)),
            Rc::new(Object::Integer(25))
        ]))
    );
}

#[test]
fn test_filter() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define odd (lambda (v) (= 1 (% v 2))))
            (define l (list 1 2 3 4 5))
            (filter odd l)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(
        result,
        Rc::new(Object::List(vec![
            Rc::new(Object::Integer(1)),
            Rc::new(Object::Integer(3)),
            Rc::new(Object::Integer(5))
        ]))
    );
}

#[test]
fn test_reduce() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define odd (lambda (v) (= 1 (% v 2))))
            (define l (list 1 2 3 4 5))
            (reduce (lambda (x y) (or x y)) (map odd l))
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Bool(true)));
}

#[test]
fn test_fibonaci() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define fib (lambda (n) 
                (if (< n 2) 1 
                    (+ (fib (- n 1)) 
                        (fib (- n 2))))))
            (fib 10)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((89) as i64)));
}

#[test]
fn test_factorial() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define fact (lambda (n) (if (< n 1) 1 (* n (fact (- n 1))))))
            (fact 5)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((120) as i64)));
}

#[test]
fn test_abs() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define abs (n) (if (< n 0) (* -1 n) n))
            (abs -5)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((5) as i64)));
}

#[test]
fn test_circle_area_no_lambda() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define pi 314)
            (define r 10)
            (define sqr (r) (* r r))
            (define area (r) (* pi (sqr r)))
            (area r)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((314 * 10 * 10) as i64)));
}

#[test]
fn test_circle_area_function() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define pi 314)
            (define r 10)
            (define sqr (lambda (r) (* r r)))
            (define area (lambda (r) (* pi (sqr r))))
            (area r)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((314 * 10 * 10) as i64)));
}

#[test]
fn test_tail_recursion() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define sum-n 
                (lambda (n a) 
                    (if (= n 0) a 
                        (sum-n (- n 1) (+ n a)))))
            (sum-n 100 0)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((5050) as i64)));
}

#[test]
fn test_tail_recursive_factorial() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define fact 
                (lambda (n a) 
                    (if (= n 1) a 
                    (fact (- n 1) (* n a)))))
                    
            (fact 10 1)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((3628800) as i64)));
}

#[test]
fn test_closure1() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define add-n 
                (lambda (n) 
                    (lambda (a) (+ n a))))
            (define add-5 (add-n 5))
            (add-5 10)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((15) as i64)));
}

#[test]
fn test_tail_recursive_fibonnaci() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (begin
            (define fib
                (lambda (n a b) 
                    (if (= n 0) a 
                    (if (= n 1) b 
                        (fib (- n 1) b (+ a b))))))
                
            (fib 10 0 1)
        )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((55) as i64)));
}

#[test]
fn test_inline_lambda() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        ((lambda (x y) (+ x y)) 10 20)
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((30) as i64)));
}

#[test]
fn test_car() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (car (list 1 2 3))
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((1) as i64)));
}

#[test]
fn test_cdr() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (cdr (list 1 2 3))
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(
        result,
        Rc::new(Object::List(vec![Rc::new(Object::Integer(2)), Rc::new(Object::Integer(3)),]))
    );
}

#[test]
fn test_length() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (length (list 1 2 3))
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((3) as i64)));
}

#[test]
fn test_sum_list_of_integers() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (define sum-list 
            (lambda (l) 
                (if (empty l) 0 
                    (+ (car l) (sum-list (cdr l))))))
        (sum-list (list 1 2 3 4 5))
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer(15)));
}

#[test]
fn test_function_application() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (define double (value) 
            (* 2 value))
        (define apply-twice (fn value) 
            (fn (fn value)))
    
        (apply-twice double 5)
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((20) as i64)));
}

#[test]
fn test_begin_scope_test() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (define a 10)
        (define b 20)
        (define c 30)
        (begin
            (define a 20)
            (define b 30)
            (define c 40)
            (list a b c)
        )
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(
        result,
        Rc::new(Object::List(vec![
            Rc::new(Object::Integer(20)),
            Rc::new(Object::Integer(30)),
            Rc::new(Object::Integer(40)),
        ]))
    );
}

#[test]
fn test_begin_scope_test_2() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin 
        (define x 10)
        (begin
            (define x 20)
            x 
        )
        x
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer((10) as i64)));
}

#[test]
fn test_let_1() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (let ((a 10) (b 20))
            (list a b)
        )
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(
        result,
        Rc::new(Object::List(vec![Rc::new(Object::Integer(10)), Rc::new(Object::Integer(20)),]))
    );
}

#[test]
fn test_let_2() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
    (begin
        (define a 100)
        (let ((a 10) (b 20))
            (list a b)
        )
        a
    )
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer(100)));
}

#[test]
fn test_let_3() {
    let mut env = Rc::new(RefCell::new(Env::new()));
    let program = "
        (let ((x 2) (y 3))
            (let ((x 7)
                    (z (+ x y)))
                (* z x))) 
    ";

    let result = eval(program, &mut env).unwrap();
    assert_eq!(result, Rc::new(Object::Integer(35)));
}
